package com.sun.gis.tools.shapefile;

import org.geotools.data.*;

import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;

import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.locationtech.jts.geom.*;
import org.opengis.feature.simple.SimpleFeature;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.*;

import org.opengis.feature.simple.SimpleFeatureType;


/**
 * MultiPolygon 多多边形空洞"缝合"（支持一个多多边形多个空洞）
 * <p>
 * 1. **读取形状文件**: 使用 GeoTools 的 `FileDataStoreFinder` 读取一个形状文件（.shp）。
 * 2. **遍历特征**: 从形状文件中提取的特征（例如，多边形）被遍历处理。
 * 3. **处理多边形**: 对于每个多边形，代码找到内外环之间最近的点，并创建一个新的多边形，这个新多边形在内外环之间添加了额外的连接线段。
 * 4. **输出GeoJSON**: 新创建的多边形被转换为 GeoJSON 格式，这是一种用于编码各种类型的地理数据结构的格式。
 */
public class ShapefileProcessor {

    public static void main(String[] args) {

        // 输入文件
        String inputShapeFile = "/Users/sungang/Desktop/项目/农业保险项目/乌拉特前旗_乔木林数据_示例/tree.shp";
        // 输出文件路径
        String outputShapeFile = "/Users/sungang/Desktop/项目/农业保险项目/乌拉特前旗_乔木林数据_示例/output2.shp";


        try {
            // 读取输入的 Shapefile
            File file = new File(inputShapeFile);
            FileDataStore store = FileDataStoreFinder.getDataStore(file);
            ((ShapefileDataStore) store).setCharset(StandardCharsets.UTF_8); // 设置编码为 UTF-8
            SimpleFeatureSource featureSource = store.getFeatureSource();


            // 获取 Shapefile 的所有特征
            SimpleFeatureCollection collection = featureSource.getFeatures();

            // 创建新的特征集合用于存储修改后的特征
            DefaultFeatureCollection newCollection = new DefaultFeatureCollection();
            SimpleFeatureType sft = featureSource.getSchema();

            // 遍历所有特征
            try (SimpleFeatureIterator features = collection.features()) {
                while (features.hasNext()) {
                    SimpleFeature feature = features.next();

                    // 假设 stitchFeature 是自定义方法，用于处理特征
                    Polygon stitchedPolygon = stitchFeature(feature);

                    // 创建新的特征并添加到新的集合中
//                    SimpleFeature newFeature = createNewSimpleFeature(stitchedPolygon, sft);
                    SimpleFeature newFeature = createNewSimpleFeature(stitchedPolygon, feature, sft);
                    newCollection.add(newFeature);
                }
            }

            // 保存修改后的特征到新的 Shapefile
            saveAsShapefile(newCollection, outputShapeFile);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    /**
     * 创建一个新的 SimpleFeature，根据修改后的多边形和特征类型
     *
     * @param polygon 要转换的多边形
     * @param sft     SimpleFeatureType 定义了特征的结构
     * @return 新创建的 SimpleFeature
     */
    public static SimpleFeature createNewSimpleFeature(Polygon polygon, SimpleFeatureType sft) {
        SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(sft);
        featureBuilder.add(polygon);
        return featureBuilder.buildFeature(null);
    }

    public static SimpleFeature createNewSimpleFeature(Polygon polygon, SimpleFeature originalFeature, SimpleFeatureType sft) {
        SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(sft);
        featureBuilder.addAll(originalFeature.getAttributes()); // 复制所有原始特征的属性
        featureBuilder.set("the_geom", polygon); // 设置几何属性为新的多边形
        return featureBuilder.buildFeature(null);
    }


    /**
     * 找到两个线性环之间最近的点对
     *
     * @param ring1 第一个线性环
     * @param ring2 第二个线性环
     * @return 最近的点对
     */
    private static Coordinate[] findClosestPoints(LinearRing ring1, LinearRing ring2) {
        double minDistance = Double.MAX_VALUE;
        Coordinate closestPoint1 = null;
        Coordinate closestPoint2 = null;

        for (Coordinate coord1 : ring1.getCoordinates()) {
            for (Coordinate coord2 : ring2.getCoordinates()) {
                double distance = coord1.distance(coord2);
                if (distance < minDistance) {
                    minDistance = distance;
                    closestPoint1 = coord1;
                    closestPoint2 = coord2;
                }
            }
        }

        return new Coordinate[]{closestPoint1, closestPoint2};
    }


    /**
     * 处理并“缝合”一个多边形的内外环
     *
     * @param feature 要处理的地理特征
     * @return 处理后的 Polygon 对象
     */
    public static Polygon stitchFeature(SimpleFeature feature) {
        Geometry geometry = (Geometry) feature.getDefaultGeometry();
        if (geometry instanceof MultiPolygon) {
            MultiPolygon multiPolygon = (MultiPolygon) geometry;
            if (multiPolygon.getNumGeometries() > 1) {
                // 打印多边形信息
                System.out.println("Feature ID: " + feature.getID());
            }
            for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
                Polygon polygon = (Polygon) multiPolygon.getGeometryN(i);
                return stitchPolygon(polygon);
            }
        }
        return null;
    }


    /**
     * 连接多边形的内外环，并返回新的多边形
     *
     * @param polygon 要处理的多边形
     * @return 新的多边形
     */
    private static Polygon stitchPolygon(Polygon polygon) {
        GeometryFactory geometryFactory = new GeometryFactory();
        LinearRing shell = polygon.getExteriorRing();
        List<Coordinate> stitchedCoordinates = new ArrayList<>(Arrays.asList(shell.getCoordinates()));
        // 处理每个内环
        for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
            LinearRing hole = polygon.getInteriorRingN(i);
            Coordinate[] closestPoints = findClosestPoints(shell, hole);

            // 在外环和内环之间插入连接点
            insertStitchPoints(stitchedCoordinates, hole, closestPoints);
        }
        // 确保闭合
        if (!stitchedCoordinates.get(0).equals2D(stitchedCoordinates.get(stitchedCoordinates.size() - 1))) {
            stitchedCoordinates.add(stitchedCoordinates.get(0));
        }

        LinearRing newShell = geometryFactory.createLinearRing(stitchedCoordinates.toArray(new Coordinate[0]));
        return geometryFactory.createPolygon(newShell);
    }


    /**
     * 在坐标列表中插入连接点
     *
     * @param stitchedCoordinates 缝合后的坐标列表
     * @param hole                内环
     * @param closestPoints       最近的点对
     */
    private static void insertStitchPoints(List<Coordinate> stitchedCoordinates, LinearRing hole, Coordinate[] closestPoints) {
        int shellIndex = findCoordinateIndex(stitchedCoordinates.toArray(new Coordinate[0]), closestPoints[0]);
        int holeIndex = findCoordinateIndex(hole.getCoordinates(), closestPoints[1]);

        // 旋转内环坐标，使最近点位于起始位置
        List<Coordinate> holeCoordinates = new ArrayList<>(Arrays.asList(hole.getCoordinates()));
        Collections.rotate(holeCoordinates, -holeIndex);
        // 确保内环坐标闭合
        if (!holeCoordinates.get(0).equals2D(holeCoordinates.get(holeCoordinates.size() - 1))) {
            holeCoordinates.add(holeCoordinates.get(0));
        }

        // 在外环中插入内环连接点
        stitchedCoordinates.add(shellIndex + 1, closestPoints[1]);
        // 插入内环坐标
        stitchedCoordinates.addAll(shellIndex + 2, holeCoordinates);
        // 再次插入外环连接点，回到外环
        stitchedCoordinates.add(shellIndex + 2 + holeCoordinates.size(), closestPoints[0]);
    }


    private static int findCoordinateIndex(Coordinate[] coordinates, Coordinate target) {
        for (int i = 0; i < coordinates.length; i++) {
            if (coordinates[i].equals2D(target)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 保存结果到新的 Shapefile 文件
     *
     * @param modifiedFeatures 修改后的地理特征集合
     * @param newShapefilePath 要保存的新 Shapefile 文件路径
     */
    public static void saveAsShapefile(SimpleFeatureCollection modifiedFeatures, String newShapefilePath) {
        try {
            // 创建 .cpg 文件
            createCpgFile(newShapefilePath, "UTF-8");
            // 定义 Shapefile 文件的存储
            File file = new File(newShapefilePath);
            Map<String, Serializable> params = new HashMap<>();
            params.put(ShapefileDataStoreFactory.URLP.key, file.toURI().toURL());
            params.put(ShapefileDataStoreFactory.CREATE_SPATIAL_INDEX.key, Boolean.TRUE);

            ShapefileDataStore newDataStore = (ShapefileDataStore) new ShapefileDataStoreFactory().createNewDataStore(params);
            newDataStore.setCharset(StandardCharsets.UTF_8); // 确保编码为 UTF-8


            SimpleFeatureType sft = modifiedFeatures.getSchema();

            // 创建新的特征类型（根据修改后的特征）
            SimpleFeatureTypeBuilder stb = new SimpleFeatureTypeBuilder();
            stb.init(sft);
            stb.setName("NewFeatureType");
            SimpleFeatureType newFeatureType = stb.buildFeatureType();

            // 设置特征类型和坐标参考系统
            newDataStore.createSchema(newFeatureType);

            // 写入修改后的特征到新的 Shapefile
            Transaction transaction = new DefaultTransaction("create");

            String typeName = newDataStore.getTypeNames()[0];
            SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);

            if (featureSource instanceof SimpleFeatureStore) {
                SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;

                featureStore.setTransaction(transaction);
                try {
                    featureStore.addFeatures(modifiedFeatures);
                    transaction.commit();
                } catch (Exception problem) {
                    transaction.rollback();
                } finally {
                    transaction.close();
                }
            } else {
                System.out.println(typeName + " does not support read/write access");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private static void createCpgFile(String shapefilePath, String charset) {
        try {
            // 假设 shapefilePath 为 "path/to/shapefile.shp"
            // 则 .cpg 文件路径应为 "path/to/shapefile.cpg"
            String cpgFilePath = shapefilePath.replace(".shp", ".cpg");

            try (BufferedWriter writer = new BufferedWriter(new FileWriter(cpgFilePath))) {
                writer.write(charset);
                System.out.println("Successfully created .cpg file.");
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("Failed to create .cpg file.");
        }
    }

}



